﻿using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Generator
{
    class Node
    {
        public Node(string op, params Node[] children)
        {
            Op = op;
            Children = children;
        }

        public Node()
            : this(null)
        {
        }

        public string Label { get; set; }
        public readonly Node[] Children;
        public readonly string Op;
    }

    internal class NodePrinter
    {
        public NodePrinter(IPrinter printer)
        {
            _printer = printer;
        }

        private void PrintNode(Node node)
        {
            if (node.Label != null)
                _printer.Print(node.Label + ":");

            if (node.Op.StartsWith("SEL"))
            {
                PrintNode(node.Children[0]);

                _printer.Print(node.Op);

                toPrint.Enqueue(node.Children[1]);
                toPrint.Enqueue(node.Children[2]);
            }
            else
            {
                foreach (var c in node.Children)
                    PrintNode(c);
                _printer.Print(node.Op);
            }

            if (node.Label != null)
                _printer.Print("JOIN");
        }

        public void PrintHierarchy(Node root)
        {
            PrintNode(root);
            _printer.Print("RTN");
            _printer.Print("");

            while (toPrint.Count != 0)
            {
                PrintNode(toPrint.Dequeue());
            }
        }

        private readonly Queue<Node> toPrint = new Queue<Node>();
        private readonly IPrinter _printer;
    }

    struct Closure
    {
        public string label;
        public Function function;
    }

    class FuncsPrint : IFuncs
    {
        public FuncsPrint(IPrinter printer)
        {
            _printer = printer;
        }

        public object Const(int a)
        {
            //_printer.Print("LDC {0}  ; constant {0}", a);
            return new Node(string.Format("LDC {0}  ; constant {0}", a));
        }

        public object Param(object[] paramsList, int number)
        {
            //_printer.Print("LD 0 {0}  ; {0}th parameter", number);
            return new Node(string.Format("LD 0 {0}  ; {0}th parameter", number));
        }

        public object ElemInt(object list, object number)
        {
            return new Node("LDF _extract_int\nAP 2", (Node)list, (Node)number);
        }

        public object ElemList(object list, object number)
        {
            return new Node("LDF _extract_list\nAP 2", (Node)list, (Node)number);
        }

        public object Add(object a, object b)
        {
            //_printer.Print("ADD");
            return new Node("ADD", (Node)a, (Node)b);
        }

        public object Sub(object a, object b)
        {
            //_printer.Print("SUB");
            return new Node("SUB", (Node)a, (Node)b);
        }

        public object Mul(object a, object b)
        {
            //_printer.Print("MUL");
            return new Node("MUL", (Node)a, (Node)b);
        }

        public object Div(object a, object b)
        {
            //_printer.Print("DIV");
            return new Node("DIV", (Node)a, (Node)b);
        }

        public object MakePair(object a, object b)
        {
            return new Node("CONS", (Node)a, (Node)b);
        }

        public object MakeClosure(string name, Function func)
        {
            _AddClosure(name, func);
            return new Node("LDF " + name);
        }

        public object Call(Function func, params object[] args)
        {
            _AddClosure(func.Method.Name, func);
            return new Node(string.Format("LDF {0}\nAP {1}", func.Method.Name, args.Length), args.Cast<Node>().ToArray());
        }

        public object IfEqual(object a, object b)
        {
            //_printer.Print("CEQ");
            return new Node("CEQ", (Node)a, (Node)b);
        }

        public object IfGreater(object a, object b)
        {
            //_printer.Print("CGT");
            return new Node("CGT", (Node)a, (Node)b);
        }

        public object IfGreaterOrEqual(object a, object b)
        {
            //_printer.Print("CGTE");
            return new Node("CGTE", (Node)a, (Node)b);
        }

        public object Select(object cond, object ifTrue, object ifFalse)
        {
            string labelTrue = _NewLabel();
            ((Node)ifTrue).Label = labelTrue;
            string labelFalse = _NewLabel();
            ((Node)ifFalse).Label = labelFalse;
            //_printer.Print("SEL {0} {1}", labelTrue, ((JoinerPrint)ifFalse).Label);
            return new Node(string.Format("SEL {0} {1}", labelTrue, labelFalse), (Node)cond, (Node)ifTrue, (Node)ifFalse);
        }

        public object Return(object a)
        {
            new NodePrinter(_printer).PrintHierarchy((Node)a);
            //_printer.Print("RTN");
            _printer.Print("");

            return a;
        }

        public void Flush()
        {
            var _printedClosures = new HashSet<string>();

            while (_closures.Count > 0)
            {
                Dictionary<string, Function> closures = _closures;
                _closures = new Dictionary<string, Function>();

                foreach (var closure in closures)
                {
                    if (!_printedClosures.Contains(closure.Key))
                    {
                        _printedClosures.Add(closure.Key);
                        _printer.Print(closure.Key + ":");
                        closure.Value.Invoke(null);
                    }
                }
            }      
            _WriteCommons();  
        }

        private string _NewLabel()
        {
            return "label" + (_labelCount++);
        }

        private void _AddClosure(string name, Function func)
        {
            if (!_closures.ContainsKey(name))
                _closures[name] = func;
        }

        private void _WriteCommons()
        {
            _printer.Print(File.ReadAllText("commons.gcc"));
        }

        private int _labelCount = 0;
        private readonly IPrinter _printer;
        private Dictionary<string, Function> _closures = new Dictionary<string, Function>();
    }
}
